import socket
import time
from datetime import datetime, timedelta
import threading
import json
import secretary
import programme
import forecast
import plot
import numpy as np
import datamonitor

# Critical Section Mutual Exclusion
predic_flag_g = False
usage_data_g, solar_data_g = None, None     # declare, and wait until the begin of main function to initialize
lock = threading.Lock()

# Function for deep learn:

def model_operator(source='net'):
    """operate the models
    carry out the prediction

    Returns:
        tuple(numpy.array, numpy.array): the predict results
    """    
    global predic_flag_g
    weather_data_for_usage = forecast.get_weather_date_for_consumption(source=source)
    weather_data_for_solar = forecast.get_weather_data_for_solar(source=source)
    if (weather_data_for_usage is str) or (weather_data_for_solar is str):
        predic_flag_g = False
        return None, None
    print(f'[debug] \n\t weather data for usage:\n\t\t{weather_data_for_usage}\n')
    print(f'\tweather data for solar:\n\t\t{weather_data_for_solar}\n')
    weather_data_for_usage, weather_data_for_solar = np.array(weather_data_for_usage), np.array(weather_data_for_solar)
    usage_pred, solar_pred = programme.predict(weather_data_for_usage, weather_data_for_solar)
    predic_flag_g = True
    return usage_pred, solar_pred


def model_update():
    """update the online model

    Args:
        message (dict): the message received that night, containing the consumption data
    """    
    usage_data = usage_data_g
    consume = datamonitor.get_consume_data_24
    programme.update(usage_data, consume)
    
def get_weather():
    # 获取当前时间，设置目标时间为当天晚上 11:30
    # 预期单片机在当晚 11：45 之后连接
    boost_time = datetime.now()
    function_time = datetime(boost_time.year, boost_time.month, boost_time.day, 23, 45)

    # 计算当前时间到目标时间的秒数
    time_to_wait = (function_time - boost_time).total_seconds()

    # 如果目标时间已经过去，则设置目标时间为明天的晚上11:45
    if time_to_wait < 0:
        function_time = function_time + timedelta(days=1)
        time_to_wait = (function_time - boost_time).total_seconds()

    # 睡眠直到目标时间
    print(f"Sleeping until {function_time.strftime('%Y-%m-%d %H:%M:%S')}")
    time.sleep(time_to_wait)
    lock.acquire()
    
def attempt_task(jmessage):
    has_reply = False
    lock.acquire()
    try:
        if predic_flag_g == False:
            reply['task'] = 'auto'
            has_reply = True
        else:
            reply = get_task(jmessage)
            model_update(jmessage)
            has_reply = True
    finally:
        lock.release()
    
    return reply, has_reply

def get_task(jmessage):
    global usage_data_g
    global solar_data_g
    storage = jmessage['value']
    storage = storage / 3.6e6 * 24
    secretary.log(f'The client store {storage} kW.h')
    plan = programme.planner(storage, usage_data_g, solar_data_g)
    if plan == None:
        plan = {
            'task': 'auto'
        } 
    else:
        plan = {
            'task': 'pull power',
            'volume' : f'"{plan}"',
            'timestamp': f'{datetime.now().timestamp()}'
        }
    return plan
    

# Function for servr:

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
secretary.init()

server_socket.bind(('0.0.0.0', 1688))
server_socket.listen()
secretary.log("Listening on port 1688")

from datetime import datetime, timedelta

# 判断是否是半夜那一次汇报数据
def is_within_30_minutes_of_midnight():
    current_time = datetime.now()-timedelta(minutes=30)
    time_to_midnight = datetime.combine(current_time.date(), datetime.max.time()) - current_time
    is_within_30_minutes_of_today_midnight = time_to_midnight < timedelta(minutes=30)
    midnight_yesterday = datetime.combine(current_time.date() - timedelta(days=1), datetime.max.time())
    time_difference_to_yesterday_midnight = current_time - midnight_yesterday
    is_within_30_minutes_of_yesterday_midnight = time_difference_to_yesterday_midnight < timedelta(minutes=30)
    return is_within_30_minutes_of_today_midnight or is_within_30_minutes_of_yesterday_midnight

# 调用函数并打印结果
result = is_within_30_minutes_of_midnight()
print(result)


def waitSyncWord(client_socket:socket.socket):
    while True:
        time.sleep(0.1)
        try:
            data = client_socket.recv(1)
            if data.decode() == '`':
                break
        except:
            secretary.log('No sync word received')
            break
    return

def waitAckWord():
    while True:
        time.sleep(0.1)
        try:
            data = client_socket.recv(1)
            if data.decode() == 'A':
                break
        except:
            secretary.log('No ack word received')
            break
    return

def processPacket(packet:str):
    has_reply = False
    reply = {
        'task' : '',
    }
    secretary.log(f'Processing packet: {packet}')
    jpacket = json.loads(packet)
    if jpacket['request'] == 'finish':
        return '', True
    elif jpacket['request'] == 'push data':
        datamonitor.push_back(jpacket)
        has_reply = False
    elif jpacket['request'] == 'battery':
        reply = attempt_task(jpacket)
        has_reply = True
    elif jpacket['request'] == 'sys status':
        datamonitor.error_manage(jpacket['error'])
    elif jpacket['request'] == 'other task':
        reply['task'] = 'empty'
        has_reply = True
    ######################################
    if has_reply:
        return json.dumps(reply), False
    return '', False

def thread_client_handler(client_socket:socket.socket, client_address):
    while True:
        # 接收客户端消息
        waitSyncWord(client_socket)
        print(f"[DEBUG] Received SYN from {client_address}")
        data=b''
        data = client_socket.recv(10240)
        # 显示客户端消息
        print(f"[DEBUG] Received message")
        client_socket.sendall('A'.encode())
        print(f"[DEBUG] Sent ACK to {client_address}")
        try:
            reply, isclosing = processPacket(data.decode())
        except:
            pass
        if isclosing:
            print('[DEBUG] closing connection')
            time.sleep(1)                       # 防止同时关闭连接导致无法重连
            client_socket.close()
            break
        if len(reply) == 0:
            print('[DEBUG] no reply')
            continue
        print(f'[DEBUG] replying {reply}')
        # client_socket.send('`'.encode())
        # client_socket.send(reply.encode())
        client_socket.send(f'`{reply}'.encode())
        secretary.log(f'Replied packet {reply}')
        waitAckWord()
        print(f"[DEBUG] Received ACK from {client_address}")

usage_data_g, solar_data_g = model_operator(source='file')
print(f'[debug]\n\tusage_data_g\n\t\t{usage_data_g}\n\n\tsolar_data_g\n\t\t{solar_data_g}')
while True:
    # 接受客户端连接
    client_socket, client_address = server_socket.accept()
    secretary.log(f"Accepted connection from {client_address}")
    # 创建一个线程来处理客户端连接
    client_thread = threading.Thread(target=thread_client_handler, args=(client_socket, client_address))
    client_thread.start()
